﻿#region Copyright (c) 2013-04, Olaf Kober <amarok.projects@gmail.com>
//================================================================================
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//	copies of the Software, and to permit persons to whom the Software is
//	furnished to do so, subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in
//	all copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//	THE SOFTWARE.
//================================================================================
#endregion

#if TESTS

using System;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;


namespace Amarok.Agents
{
	[TestFixture]
	public partial class Test_Agent
	{
		private class TestMessage : Message
		{
		}

		private class TestOperation : Operation<String, Int32>
		{
		}

		private class TestInformation : Information
		{
		}

		private class MyAgentEnvironment : AgentEnvironment
		{
		}

		private class MyAgentOptions : AgentOptions
		{
		}

		private class MySubscriberAgent : Agent
		{
			public Int32 MessageHandlerCalled;
			public Int32 OperationHandlerCalled;

			public MySubscriberAgent(AgentEnvironment environment, AgentOptions options)
				: base(environment, options)
			{
			}

			[MessageHandler, ExclusiveExecution, NoParallelismAttribute]
			private void _HandleMessage(TestMessage message)
			{
				MessageHandlerCalled++;
			}

			[OperationHandler, ExclusiveExecution, NoParallelismAttribute]
			private String _HandleOperation(TestOperation message)
			{
				message.ReportProgress(75);
				OperationHandlerCalled++;
				return "done";
			}
		}

		private class MyAgent : Agent
		{
			public Int32 OnInitializeCalled;
			public Boolean ThrowExceptionInMessageFilter;


			public MyAgent(AgentEnvironment environment, AgentOptions options)
				: base(environment, options)
			{
			}


			protected override bool OnFilter(Message message)
			{
				if (ThrowExceptionInMessageFilter)
					throw new ApplicationException("ONFILTER");

				return base.OnFilter(message);
			}


			public Object TestLabel
			{
				get
				{
					return base.Label;
				}
				set
				{
					base.Label = value;
				}
			}

			public Boolean TestHasLabel
			{
				get
				{
					return base.HasLabel;
				}
			}

			public void TestResetLabel()
			{
				base.ResetLabel();
			}


			public AgentEnvironment TestEnvironment
			{
				get
				{
					return base.Environment;
				}
			}

			public AgentOptions TestOptions
			{
				get
				{
					return base.Options;
				}
			}

			public new Boolean IsCompleting
			{
				get
				{
					return base.IsCompleting;
				}
			}

			public new Task Completion
			{
				get
				{
					return base.Completion;
				}
			}

			protected override void OnInitialize()
			{
				base.OnInitialize();

				Interlocked.Increment(ref OnInitializeCalled);
			}

			public IAgent TestToAgentReference()
			{
				return base.ToAgentReference();
			}

			public void TestPost(Message message)
			{
				base.Post(message);
			}
			public void TestPost(String label, Message message)
			{
				base.Post(label, message);
			}

			public Task TestPostAfter(TimeSpan delay, Message message)
			{
				return base.PostAfter(delay, message);
			}
			public Task TestPostAfter(Int32 millisecondsDelay, Message message)
			{
				return base.PostAfter(millisecondsDelay, message);
			}
			public Task TestPostAfter(TimeSpan delay, Message message, CancellationToken cancellationToken)
			{
				return base.PostAfter(delay, message, cancellationToken);
			}
			public Task TestPostAfter(Int32 millisecondsDelay, Message message, CancellationToken cancellationToken)
			{
				return base.PostAfter(millisecondsDelay, message, cancellationToken);
			}

			public Task TestPostAfter(TimeSpan delay, Object label, Message message)
			{
				return base.PostAfter(delay, label, message);
			}
			public Task TestPostAfter(Int32 millisecondsDelay, Object label, Message message)
			{
				return base.PostAfter(millisecondsDelay, label, message);
			}
			public Task TestPostAfter(TimeSpan delay, Object label, Message message, CancellationToken cancellationToken)
			{
				return base.PostAfter(delay, label, message, cancellationToken);
			}
			public Task TestPostAfter(Int32 millisecondsDelay, Object label, Message message, CancellationToken cancellationToken)
			{
				return base.PostAfter(millisecondsDelay, label, message, cancellationToken);
			}

			public Task<TResult> TestPostOperation<TResult, TProgress>(
				Operation<TResult, TProgress> operation,
				Action<TProgress> progessAction = null)
			{
				return base.PostOperation(operation, progessAction);
			}
			public Task<TResult> TestPostOperation<TResult, TProgress>(
				String label,
				Operation<TResult, TProgress> operation,
				Action<TProgress> progessAction = null)
			{
				return base.PostOperation(label, operation, progessAction);
			}
			public Task<TResult> TestPostOperation<TResult, TProgress>(
				Operation<TResult, TProgress> operation,
				CancellationToken cancellationToken,
				Action<TProgress> progessAction = null)
			{
				return base.PostOperation(operation, cancellationToken, progessAction);
			}
			public Task<TResult> TestPostOperation<TResult, TProgress>(
				String label,
				Operation<TResult, TProgress> operation,
				CancellationToken cancellationToken,
				Action<TProgress> progessAction = null)
			{
				return base.PostOperation(label, operation, cancellationToken, progessAction);
			}

			public void TestPublish(Message message)
			{
				base.Publish(message);
			}
			public void TestPublish(String label, Message message)
			{
				base.Publish(label, message);
			}

			public Task TestPublishAfter(TimeSpan delay, Message message)
			{
				return base.PublishAfter(delay, message);
			}
			public Task TestPublishAfter(Int32 millisecondsDelay, Message message)
			{
				return base.PublishAfter(millisecondsDelay, message);
			}
			public Task TestPublishAfter(TimeSpan delay, Message message, CancellationToken cancellationToken)
			{
				return base.PublishAfter(delay, message, cancellationToken);
			}
			public Task TestPublishAfter(Int32 millisecondsDelay, Message message, CancellationToken cancellationToken)
			{
				return base.PublishAfter(millisecondsDelay, message, cancellationToken);
			}

			public Task TestPublishAfter(TimeSpan delay, Object label, Message message)
			{
				return base.PublishAfter(delay, label, message);
			}
			public Task TestPublishAfter(Int32 millisecondsDelay, Object label, Message message)
			{
				return base.PublishAfter(millisecondsDelay, label, message);
			}
			public Task TestPublishAfter(TimeSpan delay, Object label, Message message, CancellationToken cancellationToken)
			{
				return base.PublishAfter(delay, label, message, cancellationToken);
			}
			public Task TestPublishAfter(Int32 millisecondsDelay, Object label, Message message, CancellationToken cancellationToken)
			{
				return base.PublishAfter(millisecondsDelay, label, message, cancellationToken);
			}

			public Task<TResult> TestPublishOperation<TResult, TProgress>(
				Operation<TResult, TProgress> operation,
				Action<TProgress> progessAction = null)
			{
				return base.PublishOperation(operation, progessAction);
			}
			public Task<TResult> TestPublishOperation<TResult, TProgress>(
				String label,
				Operation<TResult, TProgress> operation,
				Action<TProgress> progessAction = null)
			{
				return base.PublishOperation(label, operation, progessAction);
			}
			public Task<TResult> TestPublishOperation<TResult, TProgress>(
				Operation<TResult, TProgress> operation,
				CancellationToken cancellationToken,
				Action<TProgress> progessAction = null)
			{
				return base.PublishOperation(operation, cancellationToken, progessAction);
			}
			public Task<TResult> TestPublishOperation<TResult, TProgress>(
				String label,
				Operation<TResult, TProgress> operation,
				CancellationToken cancellationToken,
				Action<TProgress> progessAction = null)
			{
				return base.PublishOperation(label, operation, cancellationToken, progessAction);
			}

			public ICompletable TestSubscribe(Type messageType, AgentReference subscriber, Predicate<Message> messageFilter)
			{
				return base.Subscribe(messageType, subscriber, messageFilter);
			}
			public ICompletable TestSubscribe(Type messageType, Action<Message> subscriber, Predicate<Message> messageFilter)
			{
				return base.Subscribe(messageType, subscriber, messageFilter);
			}
			public ICompletable TestSubscribe<TMessage>(AgentReference subscriber, Predicate<TMessage> messageFilter)
				where TMessage : Message
			{
				return base.Subscribe<TMessage>(subscriber, messageFilter);
			}
			public ICompletable TestSubscribe<TMessage>(Action<TMessage> subscriber, Predicate<TMessage> messageFilter)
				where TMessage : Message
			{
				return base.Subscribe<TMessage>(subscriber, messageFilter);
			}


			public Int32 MessageHandlerCalled;
			public Int32 OperationHandlerCalled;

			[MessageHandler, ExclusiveExecution, MaximumParallelism]
			private void _Handler(TestMessage message)
			{
				MessageHandlerCalled++;
			}

			[OperationHandler, ExclusiveExecution, NoParallelism]
			private String _HandleOperation(TestOperation message)
			{
				message.ReportProgress(25);
				OperationHandlerCalled++;
				return "finished";
			}

			[InformationHandler, ConcurrentExecution, MaximumParallelism]
			private void _HandleInformation(TestInformation message)
			{
			}
		}

		private class MySlowAgent : Agent
		{
			public Int32 OnInitializeCalled;
			public Int32 OnCleanupCalled;
			public Int32 MessageHandlerCalled;


			public MySlowAgent(AgentEnvironment environment, AgentOptions options)
				: base(environment, options)
			{
			}

			public new Boolean IsCompleting
			{
				get
				{
					return base.IsCompleting;
				}
			}

			public new Task Completion
			{
				get
				{
					return base.Completion;
				}
			}

			protected override void OnInitialize()
			{
				base.OnInitialize();
				Interlocked.Increment(ref OnInitializeCalled);
			}

			protected override void OnCleanup()
			{
				base.OnCleanup();
				Interlocked.Increment(ref OnCleanupCalled);
			}

			[MessageHandler, ExclusiveExecution, MaximumParallelism]
			private void _Handler(TestMessage message)
			{
				SpinWait.SpinUntil(() => false, 1000);
				MessageHandlerCalled++;
				base.Publish(message);
			}
		}

		private class MyAgentExceptionInInitialize : Agent
		{
			public MyAgentExceptionInInitialize(AgentEnvironment environment, AgentOptions options)
				: base(environment, options)
			{
			}

			protected override void OnInitialize()
			{
				base.OnInitialize();
				throw new ApplicationException("ONINITIALIZE");
			}

			public void TestPost(Message message)
			{
				base.Post(message);
			}

			[InformationHandler, ConcurrentExecution, MaximumParallelism]
			private void _HandleInformation(TestInformation message)
			{
			}
		}

		private class MyAgentExceptionInCleanup : Agent
		{
			public MyAgentExceptionInCleanup(AgentEnvironment environment, AgentOptions options)
				: base(environment, options)
			{
			}

			protected override void OnCleanup()
			{
				base.OnCleanup();
				throw new ApplicationException("ONCLEANUP");
			}

			public void TestPost(Message message)
			{
				base.Post(message);
			}

			[InformationHandler, ConcurrentExecution, MaximumParallelism]
			private void _HandleInformation(TestInformation message)
			{
			}
		}

		private class DummyAgentFactory : IAgentFactory
		{
			public IAgent SpawnAgent(Type agentType, AgentEnvironment environment, AgentOptions options)
			{
				throw new NotImplementedException();
			}
		}


		// --- TESTS ---


		[Test]
		public void Test_Factory()
		{
			var factory1 = Agent.Factory;
			var factory2 = Agent.Factory;

			Assert.That(factory1, Is.TypeOf<DefaultAgentFactory>());
			Assert.That(factory2, Is.SameAs(factory1));
		}

		[Test]
		public void Test_SetFactory()
		{
			Assert.That(Agent.Factory, Is.TypeOf<DefaultAgentFactory>());

			var factory = new DummyAgentFactory();
			Agent.SetFactory(factory);

			Assert.That(Agent.Factory, Is.SameAs(factory));

			Agent.SetFactory(new DefaultAgentFactory());

			Assert.That(Agent.Factory, Is.TypeOf<DefaultAgentFactory>());
		}

		[Test]
		public void Test_SetFactory_Null()
		{
			Assert.That(Agent.Factory, Is.TypeOf<DefaultAgentFactory>());

			Assert.Throws<ArgumentNullException>(() =>
				{
					Agent.SetFactory(null);
				});

			Assert.That(Agent.Factory, Is.TypeOf<DefaultAgentFactory>());
		}


		[Test]
		public void Test_Construction_NullEnvironment()
		{
			Assert.Throws<ArgumentNullException>(() =>
				{
					var agent = new MyAgent(null, new AgentOptions());
				});
		}

		[Test]
		public void Test_Construction_NullOptions()
		{
			Assert.Throws<ArgumentNullException>(() =>
				{
					var agent = new MyAgent(new AgentEnvironment(), null);
				});
		}

		[Test]
		public void Test_Construction()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);

			Assert.That(agent.TestEnvironment, Is.SameAs(environment));
			Assert.That(agent.TestOptions, Is.SameAs(options));
			Assert.That(agent.IsCompleting, Is.False);
			Assert.That(agent.Completion.IsCompleted, Is.False);
		}


		[Test]
		public void Test_ToAgentReference()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);

			Assert.That(agent.TestEnvironment, Is.SameAs(environment));
			Assert.That(agent.TestOptions, Is.SameAs(options));
			Assert.That(agent.IsCompleting, Is.False);
			Assert.That(agent.Completion.IsCompleted, Is.False);

			var agentReference = agent.TestToAgentReference();

			Assert.That(agentReference, Is.Not.Null);
			Assert.That(agentReference, Is.Not.SameAs(agent));

			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));

			var message = new TestMessage();
			agentReference.Post(message);

			Helper.WaitForCondition(5000, 10, () => agent.MessageHandlerCalled == 1);
			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(1));
			Assert.That(message.Label, Is.Null);
		}

		[Test]
		public void Test_ImplicitCast_To_AgentReference()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);

			Assert.That(agent.TestEnvironment, Is.SameAs(environment));
			Assert.That(agent.TestOptions, Is.SameAs(options));
			Assert.That(agent.IsCompleting, Is.False);
			Assert.That(agent.Completion.IsCompleted, Is.False);

			AgentReference agentReference = agent;

			Assert.That(agentReference, Is.Not.Null);
			Assert.That(agentReference, Is.Not.SameAs(agent));

			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));

			var message = new TestMessage();
			agentReference.Post(message);

			Helper.WaitForCondition(5000, 10, () => agent.MessageHandlerCalled == 1);
			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(1));
			Assert.That(message.Label, Is.Null);
		}

		[Test]
		public void Test_ImplicitCast_To_AgentReference_NullAgent()
		{
			Agent agent = null;
			AgentReference agentReference = agent;

			Assert.That(agentReference, Is.Null);
		}


		[Test]
		public void Test_Post_SingleMessage_AfterDispose()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);

			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));

			Helper.Dispose(agent);

			var message = new TestMessage();
			agent.TestPost(message);

			Helper.WaitForCondition(500, 10, () => agent.MessageHandlerCalled == 1);
			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));
		}

		[Test]
		public void Test_IAgent_Post_SingleMessage()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);
			var agentInterface = (IAgent)agent;

			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));

			var message = new TestMessage();
			agentInterface.Post(message);

			Helper.WaitForCondition(5000, 10, () => agent.MessageHandlerCalled == 1);
			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(1));
		}

		[Test]
		public void Test_IAgent_Post_NullMessage()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);
			var agentInterface = (IAgent)agent;

			Assert.Throws<ArgumentNullException>(() =>
				{
					agentInterface.Post(null);
				});
		}

		[Test]
		public void Test_IAgent_Post_SingleMessage_AfterDispose()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);
			var agentInterface = (IAgent)agent;

			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));

			Helper.Dispose(agent);

			var message = new TestMessage();
			agentInterface.Post(message);

			Helper.WaitForCondition(500, 10, () => agent.MessageHandlerCalled == 1);
			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));
		}

		[Test]
		public void Test_IAgent_Post_ManyMessage()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);
			var agentInterface = (IAgent)agent;

			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));

			for (Int32 i = 0; i < 100000; i++)
			{
				var message = new TestMessage();
				agentInterface.Post(message);
			}

			Helper.WaitForCondition(10000, 10, () => agent.MessageHandlerCalled == 100000);
			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(100000));
		}

		[Test]
		public void Test_IAgent_Post_ManyMessage_DisposeAfterPost()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);
			var agentInterface = (IAgent)agent;

			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));

			for (Int32 i = 0; i < 100000; i++)
			{
				var message = new TestMessage();
				agentInterface.Post(message);
			}

			Helper.WaitForCondition(5000, 0, () => agent.MessageHandlerCalled == 1);

			Helper.Dispose(agent);

			Helper.WaitForCondition(5000, 10, () => agent.MessageHandlerCalled == 100000);
			Assert.That(agent.MessageHandlerCalled, Is.GreaterThan(1));
		}

		[Test]
		public void Test_Publish_AfterDispose()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);
			var agentInterface = (IAgent)agent;
			var subscriber = new MySubscriberAgent(environment, options);

			agentInterface.Subscribe(
				typeof(TestMessage),
				subscriber.ToAgentReference());

			Thread.Sleep(500);

			Assert.That(subscriber.MessageHandlerCalled, Is.EqualTo(0));

			agent.TestPublish(new TestMessage());

			Helper.WaitForCondition(5000, 10, () => subscriber.MessageHandlerCalled == 1);
			Assert.That(subscriber.MessageHandlerCalled, Is.EqualTo(1));

			Helper.Dispose(agent);

			agent.TestPublish(new TestMessage());

			Helper.WaitForCondition(500, 10, () => subscriber.MessageHandlerCalled == 2);
			Assert.That(subscriber.MessageHandlerCalled, Is.EqualTo(1));
		}

		[Test]
		public void Test_OnInitialize()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);

			Assert.That(agent.OnInitializeCalled, Is.EqualTo(0));

			for (Int32 i = 0; i < 10000; i++)
				agent.TestPost(new TestInformation());
			Thread.Sleep(500);

			Assert.That(agent.OnInitializeCalled, Is.EqualTo(1));
		}

		[Test]
		public void Test_OnInitialize_Exception()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgentExceptionInInitialize(environment, options);

			agent.TestPost(new TestInformation());
			Thread.Sleep(500);
		}

		[Test]
		public void Test_OnCleanup_Exception()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgentExceptionInCleanup(environment, options);

			agent.TestPost(new TestInformation());
			Helper.Dispose(agent);
		}

		[Test]
		public void Test_Complete_ProcessAvailableMessages()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MySlowAgent(environment, options);
			var agentInterface = (IAgent)agent;
			var callbackCalled = 0;

			var subscription = agentInterface.Subscribe(
				typeof(Message),
				msg => callbackCalled++);

			Assert.That(agent.OnInitializeCalled, Is.EqualTo(0));
			Assert.That(agent.OnCleanupCalled, Is.EqualTo(0));
			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));
			Assert.That(agent.IsCompleting, Is.False);
			Assert.That(agent.Completion.IsCompleted, Is.False);

			agentInterface.Post(new TestMessage());
			agentInterface.Post(new TestMessage());
			agentInterface.Post(new TestMessage());
			agentInterface.Post(new TestMessage());
			agentInterface.Post(new TestMessage());

			Thread.Sleep(1000);

			agentInterface.Complete(discardAvailableMessages: false);

			Assert.That(agent.IsCompleting, Is.True);
			Assert.That(agent.Completion.IsCompleted, Is.False);

			var result = agentInterface.Completion.Wait(7000);

			Assert.That(agent.IsCompleting, Is.True);
			Assert.That(agent.Completion.IsCompleted, Is.True);

			Thread.Sleep(1000);

			Assert.That(result, Is.True);
			Assert.That(agent.OnInitializeCalled, Is.EqualTo(1));
			Assert.That(agent.OnCleanupCalled, Is.EqualTo(1));
			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(5));
			Assert.That(callbackCalled, Is.EqualTo(5));

			agentInterface.Dispose();
		}

		[Test]
		public void Test_Complete_DiscardAvailableMessages()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MySlowAgent(environment, options);
			var agentInterface = (IAgent)agent;
			var callbackCalled = 0;

			var subscription = agentInterface.Subscribe(
				typeof(Message),
				msg => callbackCalled++);

			Assert.That(agent.OnInitializeCalled, Is.EqualTo(0));
			Assert.That(agent.OnCleanupCalled, Is.EqualTo(0));
			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));
			Assert.That(agent.IsCompleting, Is.False);
			Assert.That(agent.Completion.IsCompleted, Is.False);

			agentInterface.Post(new TestMessage());
			agentInterface.Post(new TestMessage());
			agentInterface.Post(new TestMessage());
			agentInterface.Post(new TestMessage());
			agentInterface.Post(new TestMessage());

			Thread.Sleep(1000);

			agentInterface.Complete(discardAvailableMessages: true);

			Assert.That(agent.IsCompleting, Is.True);
			Assert.That(agent.Completion.IsCompleted, Is.False);

			var result = agentInterface.Completion.Wait(2000);

			Assert.That(agent.IsCompleting, Is.True);
			Assert.That(agent.Completion.IsCompleted, Is.True);

			Thread.Sleep(1000);

			Assert.That(result, Is.True);
			Assert.That(agent.OnInitializeCalled, Is.EqualTo(1));
			Assert.That(agent.OnCleanupCalled, Is.EqualTo(1));
			Assert.That(agent.MessageHandlerCalled, Is.InRange(1, 2));
			Assert.That(callbackCalled, Is.InRange(1, 2));

			agentInterface.Dispose();
		}

		[Test]
		public void Test_Dispose()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MySlowAgent(environment, options);
			var agentInterface = (IAgent)agent;
			var callbackCalled = 0;

			var subscription = agentInterface.Subscribe(
				typeof(Message),
				msg => callbackCalled++);

			Assert.That(agent.OnInitializeCalled, Is.EqualTo(0));
			Assert.That(agent.OnCleanupCalled, Is.EqualTo(0));
			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));
			Assert.That(agent.IsCompleting, Is.False);
			Assert.That(agent.Completion.IsCompleted, Is.False);

			agentInterface.Post(new TestMessage());
			agentInterface.Post(new TestMessage());
			agentInterface.Post(new TestMessage());
			agentInterface.Post(new TestMessage());
			agentInterface.Post(new TestMessage());

			Helper.WaitForCondition(5000, 0, () => callbackCalled == 1);

			agentInterface.Dispose();

			Assert.That(agent.IsCompleting, Is.True);
			Assert.That(agent.Completion.IsCompleted, Is.True);

			Thread.Sleep(1000);

			Assert.That(agent.OnInitializeCalled, Is.EqualTo(1));
			Assert.That(agent.OnCleanupCalled, Is.EqualTo(1));
			Assert.That(agent.MessageHandlerCalled, Is.InRange(1, 2));
			Assert.That(callbackCalled, Is.InRange(1, 2));

			agentInterface.Dispose();
		}

		[Test]
		public void Test_Complete_ProcessAvailableMessages_CompleteSubscription()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MySlowAgent(environment, options);
			var agentInterface = (IAgent)agent;
			var callbackCalled = 0;

			var subscription = agentInterface.Subscribe(
				typeof(Message),
				msg => callbackCalled++);

			Assert.That(agent.OnInitializeCalled, Is.EqualTo(0));
			Assert.That(agent.OnCleanupCalled, Is.EqualTo(0));
			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));
			Assert.That(agent.IsCompleting, Is.False);
			Assert.That(agent.Completion.IsCompleted, Is.False);

			agentInterface.Post(new TestMessage());
			agentInterface.Post(new TestMessage());
			agentInterface.Post(new TestMessage());
			agentInterface.Post(new TestMessage());
			agentInterface.Post(new TestMessage());

			Thread.Sleep(1000);

			subscription.Complete(discardAvailableMessages: false);
			agentInterface.Complete(discardAvailableMessages: false);

			Assert.That(agent.IsCompleting, Is.True);
			Assert.That(agent.Completion.IsCompleted, Is.False);

			var result = agentInterface.Completion.Wait(7000);

			Assert.That(agent.IsCompleting, Is.True);
			Assert.That(agent.Completion.IsCompleted, Is.True);

			Thread.Sleep(1000);

			Assert.That(result, Is.True);
			Assert.That(agent.OnInitializeCalled, Is.EqualTo(1));
			Assert.That(agent.OnCleanupCalled, Is.EqualTo(1));
			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(5));
			Assert.That(callbackCalled, Is.InRange(0, 1));

			agentInterface.Dispose();
		}

		[Test]
		public void Test_Concurrent_Complete()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MySlowAgent(environment, options);
			var agentInterface = (IAgent)agent;
			var callbackCalled = 0;

			var subscription = agentInterface.Subscribe(
				typeof(Message),
				msg => callbackCalled++);

			Assert.That(agent.OnInitializeCalled, Is.EqualTo(0));
			Assert.That(agent.OnCleanupCalled, Is.EqualTo(0));
			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));
			Assert.That(agent.IsCompleting, Is.False);
			Assert.That(agent.Completion.IsCompleted, Is.False);

			agentInterface.Post(new TestMessage());
			agentInterface.Post(new TestMessage());
			agentInterface.Post(new TestMessage());
			agentInterface.Post(new TestMessage());
			agentInterface.Post(new TestMessage());

			Thread.Sleep(1000);

			agentInterface.Complete(discardAvailableMessages: false);
			TaskHelper.Run(() => agentInterface.Complete(discardAvailableMessages: true));
			TaskHelper.Run(() => agentInterface.Complete(discardAvailableMessages: true));
			TaskHelper.Run(() => agentInterface.Complete(discardAvailableMessages: true));
			TaskHelper.Run(() => agentInterface.Complete(discardAvailableMessages: true));

			Assert.That(agent.IsCompleting, Is.True);
			Assert.That(agent.Completion.IsCompleted, Is.False);

			var result = agentInterface.Completion.Wait(7000);

			Assert.That(agent.IsCompleting, Is.True);
			Assert.That(agent.Completion.IsCompleted, Is.True);

			Thread.Sleep(1000);

			Assert.That(result, Is.True);
			Assert.That(agent.OnInitializeCalled, Is.EqualTo(1));
			Assert.That(agent.OnCleanupCalled, Is.EqualTo(1));
			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(5));
			Assert.That(callbackCalled, Is.EqualTo(5));
		}


		[Test]
		public void Test_Construction_Label()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);

			Assert.That(agent.TestLabel, Is.Null);
			Assert.That(agent.TestHasLabel, Is.False);
		}

		[Test]
		public void Test_Label()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);

			Assert.That(agent.TestLabel, Is.Null);
			Assert.That(agent.TestHasLabel, Is.False);

			agent.TestLabel = "COM1";

			Assert.That(agent.TestLabel, Is.EqualTo("COM1"));
			Assert.That(agent.TestHasLabel, Is.True);

			Assert.Throws<InvalidOperationException>(() =>
			{
				agent.TestLabel = "COM2";
			});

			Assert.That(agent.TestLabel, Is.EqualTo("COM1"));
			Assert.That(agent.TestHasLabel, Is.True);
		}

		[Test]
		public void Test_Label_SetNull()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);

			Assert.Throws<ArgumentNullException>(() =>
			{
				agent.TestLabel = null;
			});

			agent.TestLabel = "COM1";

			Assert.Throws<ArgumentNullException>(() =>
			{
				agent.TestLabel = null;
			});
		}

		[Test]
		public void Test_ResetLabel()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);

			Assert.That(agent.TestLabel, Is.Null);
			Assert.That(agent.TestHasLabel, Is.False);

			agent.TestResetLabel();

			Assert.That(agent.TestLabel, Is.Null);
			Assert.That(agent.TestHasLabel, Is.False);

			agent.TestLabel = "COM1";

			Assert.That(agent.TestLabel, Is.EqualTo("COM1"));
			Assert.That(agent.TestHasLabel, Is.True);

			agent.TestResetLabel();

			Assert.That(agent.TestLabel, Is.Null);
			Assert.That(agent.TestHasLabel, Is.False);

			agent.TestLabel = "COM2";

			Assert.That(agent.TestLabel, Is.EqualTo("COM2"));
			Assert.That(agent.TestHasLabel, Is.True);
		}


		[Test]
		public void Test_DefaultLabelFilter_AgentWithoutLabel_MessageWithoutLabel()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);
			var message = new TestMessage();

			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));

			message.ResetLabel();
			agent.TestResetLabel();

			agent.TestPost(message);

			Helper.WaitForCondition(5000, 10, () => agent.MessageHandlerCalled == 1);
			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(1));
		}

		[Test]
		public void Test_DefaultLabelFilter_AgentWithLabel_MessageWithoutLabel()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);
			var message = new TestMessage();

			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));

			message.ResetLabel();
			agent.TestLabel = "COM1";

			agent.TestPost(message);

			Helper.WaitForCondition(5000, 10, () => agent.MessageHandlerCalled == 1);
			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(1));
		}

		[Test]
		public void Test_DefaultLabelFilter_AgentWithoutLabel_MessageWithLabel()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);
			var message = new TestMessage();

			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));

			message.Label = "COM1";
			agent.TestResetLabel();

			agent.TestPost(message);

			Helper.WaitForCondition(5000, 10, () => agent.MessageHandlerCalled == 1);
			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(1));
		}

		[Test]
		public void Test_DefaultLabelFilter_AgentWithLabel_MessageWithLabel_LabelsMatching()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);
			var message = new TestMessage();

			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));

			message.Label = "COM1";
			agent.TestLabel = "COM1";

			agent.TestPost(message);

			Helper.WaitForCondition(5000, 10, () => agent.MessageHandlerCalled == 1);
			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(1));
		}

		[Test]
		public void Test_DefaultLabelFilter_AgentWithLabel_MessageWithLabel_Labels_Not_Matching()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);
			var message = new TestMessage();

			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));

			message.Label = "COM1";
			agent.TestLabel = "COM2";

			agent.TestPost(message);

			Helper.WaitForCondition(500, 10, () => agent.MessageHandlerCalled == 1);
			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));
		}

		[Test]
		public void Test_MessageFilter_Throwing_Exception()
		{
			var environment = new MyAgentEnvironment();
			var options = new MyAgentOptions();
			var agent = new MyAgent(environment, options);
			var message = new TestMessage();

			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));

			agent.ThrowExceptionInMessageFilter = true;

			agent.TestPost(message);

			Helper.WaitForCondition(500, 10, () => agent.MessageHandlerCalled == 1);
			Assert.That(agent.MessageHandlerCalled, Is.EqualTo(0));
		}

	}
}

#endif
